import { dirname } from "path";
import {
    longCodeTextToMetadataLanguage,
    shortCodeTextToMetadataLanguage
} from "../../common/languages";
import {
    stringEqual,
    stringIsFloat,
    stringIsInteger,
    stringIsNumber,
    stringToFloat,
    stringToInteger,
    stringToNumber
} from "../../common/util";
import { DefaultOptions, MDMLoaderOptions, Mutable } from "../../common/types";
import { createMDMFactory } from "./factory";
import * as t from "./types";
import {
    bitAnd,
    getUuid,
    isMDMCompound,
    isMDMDocument,
    isMDMElements,
    isMDMField,
    isMDMPage,
    isMDMVariable,
    joinString,
    metadataCategoryElementTypeToElementType,
    metadataFieldFlagToDataType,
    metadataLabelTypeFlagToString,
    metadataSortOrderToDisplayOrders,
    metadataUsageTypeToVariableUsageType,
    metadataUserContextFlagToString,
    stringToAlignment,
    stringToBoolean,
    stringToBorderStyle,
    stringToControlType,
    stringToCursorType,
    stringToDisplayOrder,
    stringToElementAlign,
    stringToImagePosition,
    stringToInfoType,
    stringToOrientation,
    stringToVerticalAlign,
    xmlValueToMdmValue,
    xmlValueToMdmValueNoBoolean
} from "./util";
import {
    filterElements,
    forEachChildElement,
    getElementAttribute,
    getFirstChildByTagName,
    getInnerText,
    readXml,
    readXmlContent
} from "./xml";
import { Diagnostic, DiagnosticFlag, DiagnosticMessage, errorMessages } from "../diagnostic";
import { t as types } from "../analysis";
import { initalizeMDMDocument } from "./initial";

type SpecialPropertyCallback = (property: t.Property) => boolean;
type Action = () => void;

export default class MDMLoader implements t.MDMLoader {

    private _options: MDMLoaderOptions;
    public readonly factory: t.MDMFactory;
    // Action
    private delayedActions: Action[] = [];
    // errors
    // 读取XML时的错误
    public readonly xmlErrors: Diagnostic[] = [];
    // 读取脚本对象时的错误
    public readonly scriptErrors: Diagnostic[] = [];
    // document
    private _document: types.MDMDocument | undefined;
    // state
    private _state: Mutable<t.MDMLoaderFromScriptState>;

    constructor(options: MDMLoaderOptions) {
        this._options = options;
        this.factory = createMDMFactory(this._options);
        this._state = {
            currentContext: this._options.defaultContext,
            currentLabelType: this._options.defaultLabelType,
            currentLanguage: this._options.defaultLanguage,
        };
    }

    public clear(): void {
        this.xmlErrors.splice(0);
        this.scriptErrors.splice(0);
    }

    private delay(action: Action) {
        this.delayedActions.push(action);
    }

    private executeDelayedAction() {
        for (const action of this.delayedActions) {
            action();
        }
    }

    private clearDelayedAction() {
        this.delayedActions.splice(0);
    }

    private error(diagnostic: DiagnosticMessage, range: { start: number, length: number }, ...messages: string[]): void;
    private error(diagnostic: DiagnosticMessage, ...messages: string[]): void;
    private error(diagnostic: DiagnosticMessage, arg1: string | { start: number, length: number } | undefined, ...messages: string[]) {
        let message = diagnostic.template;
        if (typeof arg1 === "string") {
            let args: string[] = [ arg1, ...messages ];
            args.forEach((v, i) => message = message.replace(`{${i.toString()}}`, v));
            this.xmlErrors.push({
                flag: DiagnosticFlag.error,
                code: diagnostic.code,
                start: -1,
                length: 0,
                message
            });
        }
        else if (arg1) {
            messages.forEach((v, i) => message = message.replace(`{${i.toString()}}`, v));
            this.scriptErrors.push({
                flag: DiagnosticFlag.error,
                code: diagnostic.code,
                start: arg1.start,
                length: arg1.length,
                message
            });
        }
    }

    private loadMDMList<T>(element: Element, childTag: string, loader: (e: Element) => T | undefined): t.MDMListOf<T> {
        let deleted: T[] | undefined = undefined;
        let items: T[] = [];
        let deletedNode = getFirstChildByTagName(element, "deleted");
        if (deletedNode) {
            deleted = [];
            const nodes = deletedNode.getElementsByTagName(childTag);
            for (let i = 0; i < nodes.length; i++) {
                let del = loader.bind(this)(nodes[i]);
                if (del) {
                    deleted.push(del);
                }
            }
        }
        const childElements = element.getElementsByTagName(childTag);
        for (let i = 0; i < childElements.length; i++) {
            let item = loader.bind(this)(childElements[i]);
            if (item) {
                items.push(item);
            }
        }
        return this.factory.createMDMList(items, deleted);
    }

    private loadContextMapEntries<T>(element: Element, loadTag: string, loader: (e: Element) => T): t.ContextMapItem<T>[] {
        const map: t.ContextMapItem<T>[] = [];
        forEachChildElement(element, ele => {
            if (ele.tagName !== loadTag) {
                return;
            }
            const context = ele.getAttribute("context");
            if (!context) {
                return;
            }
            const loaded = loader(ele);
            const existContext = map.find(e => stringEqual(e.context, context));
            if (existContext) {
                (existContext as Mutable<t.ContextMapItem<T>>).value = loaded;
            }
            else {
                map.push({ context, value: loaded });
            }
        }, this);
        return map;
    }

    private loadTypes<T extends t.IMDMObject>(element: Element | undefined, parent: any, elementTag: string | undefined, loader: (e: Element) => T | undefined): t.Types<T> {
        if (!element) {
            return this.factory.createTypes(parent);
        }
        const items: [string, T][] = [];
        const id = getElementAttribute(element, "id");

        let deletedNode: Element | undefined;
        let unversionedNode: Element | undefined;

        forEachChildElement(element, e => {
            if (e.tagName === "deleted") {
                deletedNode = e;
            }
            else if (e.tagName === "unversioned") {
                unversionedNode = e;
            }
            else if (!elementTag || e.tagName === elementTag) {
                let item = loader(e);
                if (item) {
                    items.push([item.uuid, item]);
                }
            }
            else {
                this.error(errorMessages.unknownChildElementTag, element.tagName, e.tagName);
            }
        }, this);
        const types = this.factory.createTypes(parent, id, items) as Mutable<t.Types<T>>;
        // deleted
        if (deletedNode) {
            let deletedEntries: [string, T][] = [];
            forEachChildElement(deletedNode, e => {
                let deletedItem = loader(e);
                if (deletedItem) {
                    deletedEntries.push([deletedItem.uuid, deletedItem]);
                }
            }, this);
            types.deleted = this.factory.createTypes(types, undefined, deletedEntries);
        }
        // unversioned
        if (unversionedNode) {
            let unversionedEntries: [string, T][] = [];
            forEachChildElement(unversionedNode, e => {
                let unversionedItem = loader(e);
                if (unversionedItem) {
                    unversionedEntries.push([unversionedItem.uuid, unversionedItem]);
                }
            }, this);
            types.unversioned = this.factory.createTypes(types, undefined, unversionedEntries);
        }

        return types;
    }

    private loadCellStyleProperty(cellStyle: Mutable<t.CellStyle>, element: Element): void {
        const name = element.getAttribute("name");
        const value = getElementAttribute(element, "value") ?? "";
        switch (name) {
            case "BorderColor":
                cellStyle.bgColor = value;
                break;
            case "BorderStyle":
                cellStyle.borderStyle = stringToInteger(value);
                break;
            case "CWidth":
                cellStyle.width = value;
                break;
            case "CHeight":
                cellStyle.height = value;
                break;
            case "BorderTopColor":
                cellStyle.borderTopColor = value;
                break;
            case "BorderRightColor":
                cellStyle.borderRightColor = value;
                break;
            case "BorderBottomColor":
                cellStyle.borderBottomColor = value;
                break;
            case "BorderLeftColor":
                cellStyle.borderLeftColor = value;
                break;
            case "BorderWidth":
                cellStyle.borderWidth = stringToNumber(value);
                break;
            case "BorderTopWidth":
                cellStyle.borderTopWidth = stringToNumber(value);
                break;
            case "BorderRightWidth":
                cellStyle.borderRightWidth = stringToNumber(value);
                break;
            case "BorderBottomWidth":
                cellStyle.borderBottomWidth = stringToNumber(value);
                break;
            case "BorderLeftWidth":
                cellStyle.borderLeftWidth = stringToNumber(value);
                break;
            case "BorderTopStyle":
                cellStyle.borderTopStyle = stringToInteger(value);
                break;
            case "BorderRightStyle":
                cellStyle.borderRightStyle = stringToInteger(value);
                break;
            case "BorderBottomStyle":
                cellStyle.borderBottomStyle = stringToInteger(value);
                break;
            case "BorderLeftStyle":
                cellStyle.borderLeftStyle = stringToInteger(value);
                break;
            case "Padding":
                cellStyle.padding = stringToNumber(value);
                break;
            case "PaddingTop":
                cellStyle.paddingTop = stringToNumber(value);
                break;
            case "PaddingRight":
                cellStyle.paddingRight = stringToNumber(value);
                break;
            case "PaddingBottom":
                cellStyle.paddingBottom = stringToNumber(value);
                break;
            case "PaddingLeft":
                cellStyle.paddingLeft = stringToNumber(value);
                break;
            case "Wrap":
                cellStyle.wrap = stringToBoolean(value);
                break;
            case "ColSpan":
                cellStyle.colSpan = stringToNumber(value);
                break;
            case "RowSpan":
                cellStyle.rowSpan = stringToNumber(value);
                break;
            case "CBgColor":
                cellStyle.bgColor = value;
                break;
            case "RepeatHeader":
                cellStyle.repeatHeader = stringToNumber(value);
                break;
            case "RepeatSideHeader":
                cellStyle.repeatSideHeader = stringToNumber(value);
                break;
        }
    }

    private loadCellStyle(element: Element, specifiedTag: string): t.CellStyle {
        const cellStyle = this.factory.createCellStyle(element.getAttribute("context") ?? "");
        const styles = getFirstChildByTagName(element, specifiedTag);
        if (styles) {
            const properties = styles.getElementsByTagName("property");
            for (let i = 0; i < properties.length; i++) {
                this.loadCellStyleProperty(cellStyle, properties[i]);
            }
        }
        return cellStyle;
    }

    private loadControlStyleProperty(controlStyle: Mutable<t.ControlStyle>, element: Element): void {
        const name = element.getAttribute("name");
        const value = getElementAttribute(element, "value") ?? "";
        switch (name) {
            case "Accelerator":
                controlStyle.accelerator = value;
                break;
            case "Type":
                controlStyle.type = stringToInteger(value);
                break;
            case "ReadOnly":
                controlStyle.readonly = stringToBoolean(value);
                break;
        }
    }

    private loadControlStyle(element: Element, specifiedTag: string): t.ControlStyle {
        const controlStyle = this.factory.createControlStyle(element.getAttribute("context") ?? "");
        const styles = getFirstChildByTagName(element, specifiedTag);
        if (styles) {
            const properties = styles.getElementsByTagName("property");
            for (let i = 0; i < properties.length; i++) {
                this.loadControlStyleProperty(controlStyle, properties[i]);
            }
        }
        return controlStyle;
    }

    private loadFontStyleProperty(fontStyle: Mutable<t.FontStyle>, element: Element): void {
        const name = element.getAttribute("name");
        const value = getElementAttribute(element, "value") ?? "";
        switch (name) {
            case "Family":
                fontStyle.family = value;
                break;
            case "Effects":
                fontStyle.effects = stringToNumber(value);
                break;
            case "Size":
                fontStyle.size = stringToNumber(value);
                break;
        }
    }

    private loadFontStyle(element: Element, specifiedTag: string): t.FontStyle {
        const fontStyle = this.factory.createFontStyle(element.getAttribute("context") ?? "");
        const styles = getFirstChildByTagName(element, specifiedTag);
        if (styles) {
            const properties = styles.getElementsByTagName("property");
            for (let i = 0; i < properties.length; i++) {
                this.loadFontStyleProperty(fontStyle, properties[i]);
            }
        }
        return fontStyle;
    }

    private loadStyleProperty(style: Mutable<t.Style>, element: Element, specifiedTag: string): void {
        const value = getElementAttribute(element, "value");
        switch (element.getAttribute("name")) {
            case "Cell":
                style.cell = this.loadCellStyle(element, specifiedTag);
                break;
            case "Control":
                style.control = this.loadControlStyle(element, specifiedTag);
                break;
            case "Font":
                style.font = this.loadFontStyle(element, specifiedTag);
                break;
            case "Color":
                style.color = value;
                break;
            case "BgColor":
                style.bgColor = value;
                break;
            case "Hidden":
                style.hidden = stringToBoolean(value);
                break;
            case "Align":
                style.align = stringToInteger(value);
                break;
            case "VerticalAlign":
                style.verticalAlign = stringToInteger(value);
                break;
            case "ElementAlign":
                style.elementAlign = stringToInteger(value);
                break;
            case "Indent":
                style.indent = stringToInteger(value);
                break;
            case "ZIndex":
                style.zIndex = stringToInteger(value);
                break;
            case "Cursor":
                style.cursor = stringToInteger(value);
                break;
            case "Image":
                style.image = value;
                break;
            case "ImagePosition":
                style.imagePosition = stringToInteger(value);
                break;
            case "Orientation":
                style.orientation = stringToInteger(value);
                break;
            case "Columns":
                style.columns = stringToInteger(value);
                break;
            case "Rows":
                style.rows = stringToInteger(value);
                break;
            case "Width":
                style.width = value;
                break;
            case "Height":
                style.height = value;
                break;
        }
    }

    private loadStyle(context: string, elements: Element[], specifiedTag: string): t.Style {
        const style = this.factory.createStyle(context);
        for (const item of elements) {
            this.loadStyleProperty(style, item, specifiedTag);
        }
        return style;
    }

    private loadStyles(element: Element, specifiedTag: string): t.Styles {
        const filtered = filterElements(element, "property", e => e.getAttribute("context") ?? "", this);
        const entries: t.ContextMapItem<t.Style>[] = [];
        for (const item of filtered) {
            entries.push({ context: item[0], value: this.loadStyle(item[0], item[1], specifiedTag) });
        }
        return this.factory.createContextMap(entries);
    }

    private loadProperty(document: t.BaseMDMDocument, element: Element): t.Property {
        let name = getElementAttribute(element, "name") ?? "";
        let context = getElementAttribute(element, "context") ?? "";
        let value: t.PropertyValue | undefined = getElementAttribute(element, "value");
        let type = element.getAttribute("type");
        if (!value) {
            let properties = element.getElementsByTagName("properties");
            if (properties.length > 0) {
                value = this.loadProperties(document, properties[0]);
            }
        }
        else {
            switch (type) {
                case "3":
                    value = stringToInteger(value);
                    break;
                case "5":
                    value = stringToFloat(value);
                    break;
                case "8":
                    break;
                case "11":
                    value = value === "-1" ? true : false;
                    break;
                default:
                    break;
            }
        }
        let ds = element.getAttribute("ds") ?? undefined;
        return this.factory.createProperty(name, context, value, ds);
    }

    private loadSingleProperty(document: t.BaseMDMDocument, element: Element, items: t.ContextMapItem<t.Property[]>[], versioned: boolean, specialPropertyCallback?: SpecialPropertyCallback): void {
        const property = this.loadProperty(document, element);
        if (specialPropertyCallback && specialPropertyCallback(property)) {
            return;
        }
        if (property.context.length > 0) {
            if (!versioned) {
                (property as Mutable<t.Property>).versioned = false;
            }
            const existContext = items.find(e => stringEqual(e.context, property.context));
            if (existContext) {
                existContext.value.push(property);
            }
            else {
                items.push({ context: property.context, value: [property] });
            }
        }
    }

    private loadProperties(document: t.BaseMDMDocument, element: Element, specialPropertyCallback?: SpecialPropertyCallback): t.Properties {
        const id = getElementAttribute(element, "id");
        const name = getElementAttribute(element, "name");
        const items: t.ContextMapItem<t.Property[]>[] = [];

        const unversionedNode = getFirstChildByTagName(element, "unversioned");
        if (unversionedNode) {
            forEachChildElement(unversionedNode, prop => this.loadSingleProperty(document, prop, items, false, specialPropertyCallback), this);
        }

        forEachChildElement(element, prop => this.loadSingleProperty(document, prop, items, true, specialPropertyCallback), this);

        return this.factory.createProperties(document, id, name, items);
    }

    private loadTemplate(document: t.BaseMDMDocument, element: Element): t.Template {
        return this.factory.createTemplate(document, element.getAttribute("name") ?? "", element.getAttribute("context") ?? "", getElementAttribute(element, "value") ?? "");
    }

    private loadTemplates(document: t.BaseMDMDocument, element: Element): t.Templates {
        const items: t.ContextMapItem<t.Template[]>[] = [];
        const properties = element.getElementsByTagName("property");
        for (let i = 0; i < properties.length; i++) {
            const node = properties[i];
            const template = this.loadTemplate(document, node);
            if (template.context.length > 0) {
                const existContext = items.find(e => stringEqual(e.context, template.context));
                if (existContext) {
                    existContext.value.push(template);
                }
                else {
                    items.push({ context: template.context, value: [template] });
                }
            }
        };
        return this.factory.createTemplates(document, items);
    }

    private loadPropertiesTemplatesStyles(obj: Mutable<t.IMDMLabeledObject>, element: Element, specialPropertyCallback?: SpecialPropertyCallback): void {
        // properties
        const properties = getFirstChildByTagName(element, "properties");
        if (properties) {
            obj.properties = this.loadProperties(obj.document, properties, specialPropertyCallback);
        }
        // templates
        const templates = getFirstChildByTagName(element, "templates");
        if (templates) {
            obj.templates = this.loadTemplates(obj.document, templates);
        }
        // labelstyles
        const labelStyles = getFirstChildByTagName(element, "labelstyles");
        if (labelStyles) {
            obj.labelStyles = this.loadStyles(labelStyles, "labelstyles");
        }
        // styles
        const styles = getFirstChildByTagName(element, "styles");
        if (styles) {
            obj.styles = this.loadStyles(styles, "styles");
        }
    }

    private loadLabelObject(element: Element): t.LabelObject {
        const context = element.getAttribute("context") ?? "";
        const language = element.getAttribute("xml:lang") ?? "";
        let inner = getInnerText(element);
        return this.factory.createLabelObject(context, language, inner.text, inner.cdata);
    }

    private loadLabel(document: t.BaseMDMDocument, element: Element): t.Label {
        const context = element.getAttribute("context") ?? "";
        const items = this.loadMDMList(element, "text", this.loadLabelObject.bind(this));
        return this.factory.createLabel(document, context, items);
    }

    private loadLabels(document: t.BaseMDMDocument, element: Element): t.Labels {
        return this.factory.createLabels(document, this.loadContextMapEntries(element, "labels", e => this.loadLabel(document, e)));
    }

    private loadNotes(element: Element): t.Notes {
        const notesNode = getFirstChildByTagName(element, "notes");
        const items: t.Note[] = [];
        if (notesNode) {
            const properties = notesNode.getElementsByTagName("property");
            for (let i = 0; i < properties.length; i++) {
                const prop = properties[i];
                items.push(this.factory.createNote(getElementAttribute(prop, "value") ?? "", prop.getAttribute("name") ?? "", prop.getAttribute("context") ?? ""));
            }
        }
        return this.factory.createNotes(items);
    }

    private loadLabeledObject(obj: Mutable<t.IMDMLabeledObject>, element: Element, specialPropertyCallback?: SpecialPropertyCallback): void {
        // labels
        obj.labels = this.loadLabels(obj.document, element);
        // properties templates labelstyles styles
        this.loadPropertiesTemplatesStyles(obj, element, specialPropertyCallback);
        // notes
        obj.notes = this.loadNotes(element);
    }

    private loadElement(document: t.BaseMDMDocument, parent: t.IMDMObject, owner: t.Field | undefined, element: Element): t.MDMElement {
        // element type
        let elementType = t.ElementTypes.category;
        let elementTypeValue = getElementAttribute(element, "type", stringToInteger);
        if (elementTypeValue !== undefined) {
            elementType = elementTypeValue;
        }

        // category flag
        let categoryFlag = t.CategoryFlags.none;
        // missing - noanswer / dontknow / refuse
        let missing = element.getAttribute("missing");
        if (missing) {
            switch (missing) {
                case "noanswer":
                    categoryFlag |= t.CategoryFlags.noanswer;
                    break;
                case "dontknow":
                    categoryFlag |= t.CategoryFlags.dontknow;
                    break;
                case "refuse":
                    categoryFlag |= t.CategoryFlags.refuse;
                    break;
            }
        }
        // fixed
        if (element.getAttribute("fixed") === "-1") {
            categoryFlag |= t.CategoryFlags.fixedPosition;
        }
        // nofilter
        if (element.getAttribute("nofilter") === "-1") {
            categoryFlag |= t.CategoryFlags.noFilter;
        }
        // exclusive
        if (element.getAttribute("exclusive") === "-1") {
            categoryFlag |= t.CategoryFlags.exclusive;
        }
        // other-local
        if (element.getAttribute("other-local") === "-1") {
            categoryFlag |= t.CategoryFlags.other;
        }
        // multiplier-local
        if (element.getAttribute("multiplier-local") === "-1") {
            categoryFlag |= t.CategoryFlags.multiplier;
        }

        let name = getElementAttribute(element, "name") ?? "";

        if (name === "") {
            this.error(errorMessages.missingAtrribute, element.tagName, "name");
        }

        const obj = this.factory.createMDMElement(
            document,
            parent,
            name,
            elementType,
            owner,
            getElementAttribute(element, "id"),
            categoryFlag) as Mutable<t.MDMElement>;

        // expression
        obj.expression = getElementAttribute(element, "expression");
        // keycode
        obj.keyCode = getElementAttribute(element, "keycode");
        // factor
        let factor = getElementAttribute(element, "factor-value");
        let factorValueType = getElementAttribute(element, "factor-type");
        if (factor) {
            if (factorValueType === "11") {
                obj.factor = stringToBoolean(factor);
            }
            else if (factorValueType === "8") {
                obj.factor = factor;
            }
            else {
                obj.factor = stringIsInteger(factor) ? Number.parseInt(factor) :
                    stringIsFloat(factor) ? Number.parseFloat(factor) : undefined;
            }
        }

        this.loadLabeledObject(obj, element);

        // multiplier-variable
        let multiplier = getFirstChildByTagName(element, "multiplier-variable");
        if (multiplier) {
            obj.multiplierVariable = this.loadVariable(document, obj, multiplier, t.VariableUsageFlags.variable);
        }
        // othervariable
        let otherVariable = getFirstChildByTagName(element, "othervariable");
        if (otherVariable) {
            obj.otherVariable = this.loadVariable(document, obj, otherVariable, t.VariableUsageFlags.variable);
        }

        return obj;
    }

    private loadElements(document: t.BaseMDMDocument, parent: t.IMDMObject, ownerField: t.Field | undefined, element: Element): t.MDMElements {
        let elements: Mutable<t.MDMElements>;
        let id = getElementAttribute(element, "id");
        let name = getElementAttribute(element, "name");
        // reference
        const categoriesRef = getElementAttribute(element, isMDMDocument(parent) ? "ref" : "categoriesref");
        if (categoriesRef) {
            elements = this.factory.createMDMElements(document, parent, ownerField, name, id, document.getDefinition(categoriesRef, isMDMElements)) as Mutable<t.MDMElements>;
            elements.referenceName = getElementAttribute(element, "ref_name");
            if (!elements.reference) {
                this.delay(() => {
                    elements.reference = document.getDefinition(categoriesRef, isMDMElements);
                    if (!elements.reference) {
                        this.error(errorMessages.missingReferenceCategories, categoriesRef);
                    }
                });
            }
        }
        else {
            const thisArg = this;
            // items - element / category
            elements = this.factory.createMDMElements(document, parent, ownerField, name, id, undefined);
            forEachChildElement(element, node => {
                if (node.tagName === "category" ||
                    node.tagName === "element"  ||
                    node.tagName === "categories") {
                    (elements as ReadonlyArray<t.IMDMLabeledObject> as t.IMDMLabeledObject[]).push(
                        node.tagName === "categories" ?
                        this.loadElements(document, elements, ownerField, node) :
                        this.loadElement(document, elements, ownerField, node)
                    );
                }
                else if (node.tagName === "deleted") {
                    forEachChildElement(node, deletedNode => {
                        if (deletedNode.tagName === "category" ||
                            deletedNode.tagName === "element"  ||
                            deletedNode.tagName === "categories") {
                            (elements.deleted as t.IMDMLabeledObject[]).push(
                                deletedNode.tagName === "categories" ?
                                this.loadElements(document, elements, ownerField, deletedNode) :
                                this.loadElement(document, elements, ownerField, deletedNode)
                            );
                        }
                    }, thisArg);
                }
            }, thisArg);
        }

        let globalNamespace = getElementAttribute(element, "global-name-space");
        if (!globalNamespace || globalNamespace !== "-1") {
            elements.namespace = true;
        }

        if (element.getAttribute("fixed") === "-1") {
            elements.fixed = true;
        }

        this.loadLabeledObject(elements, element, prop => {
            if (prop.name === "DisplayOrder") {
                elements.displayOrder = stringToDisplayOrder(prop.value?.toString());
                return true;
            }
            return false;
        });

        return elements;
    }

    private loadRangeValueByDataType(value: string | undefined, dataType: t.DataTypes, defaultValue: string | number): string | number {
        if (dataType === t.DataTypes.long || dataType === t.DataTypes.categorical) {
            return stringToInteger(value) ?? defaultValue;
        }
        if (dataType === t.DataTypes.double) {
            return stringToFloat(value) ?? defaultValue;
        }
        return value ?? defaultValue;
    }

    private loadRanges(element: Element, dataType: t.DataTypes): t.Ranges {
        const ranges: t.Range[] = [];
        const elements = element.getElementsByTagName("range");
        for (let i = 0; i < elements.length; i++) {
            ranges.push(this.factory.createRange(
                this.loadRangeValueByDataType(getElementAttribute(elements[i], "lowerbound"), dataType, this._options.invalidValueLowerBoundary),
                this.loadRangeValueByDataType(getElementAttribute(elements[i], "upperbound"), dataType, this._options.invalidValueUpperBoundary)
            ));
        }
        return ranges;
    }

    private loadDbProperties(element: Element): t.DBQuestionProperties | undefined {
        const properties: Mutable<t.DBQuestionProperties> = { connectionString: "", table: "" };
        const columns: Mutable<t.DBColumnMapItems> = { id: "", label: "" };

        let hasConnectionString = false;
        let hasTable = false;

        let hasColumnId = false;
        let hasColumnLabel = false;

        let propertyNodes = element.getElementsByTagName("property");
        if (propertyNodes) {
            for (let i = 0; i < propertyNodes.length; i++) {
                const node = propertyNodes[i];
                const value = getElementAttribute(node, "value");
                switch (node.getAttribute("name")) {
                    case "id":
                        if (value) {
                            columns.id = value;
                            hasColumnId = true;
                        }
                        break;
                    case "t.Label":
                        if (value) {
                            columns.label = value;
                            hasColumnLabel = true;
                        }
                    case "CacheTimeout":
                        properties.cacheTimeout = stringToInteger(value);
                        break;
                    case "MaxAnswers":
                        properties.maxAnswers = stringToInteger(value);
                        break;
                    case "MinAnswers":
                        properties.minAnswers = stringToInteger(value);
                        break;
                    case "Table":
                        if (value) {
                            properties.table = value;
                            hasTable = true;
                        }
                        break;
                    case "ConnectionString":
                        if (value) {
                            properties.connectionString = value;
                            hasConnectionString = true;
                        }
                        break;
                    case "IteratorIDType":
                        properties.iteratorIDType = value === "2" ? "text"
                        : value === "1" ? "long"
                        : value === "6" ? "double"
                        : value === "5" ? "date"
                        : undefined;
                        break;
                    case "SQLFilter":
                        properties.sqlFilter = value;
                        break;
                    case "KeyCode":
                        columns.keyCode = value;
                        break;
                    case "File":
                        columns.file = value;
                        break;
                    case "AnalysisValue":
                        columns.analysisValue = value;
                        break;
                    case "Fixed":
                        columns.fixed = value;
                        break;
                    case "Exclusive":
                        columns.exclusive = value;
                        break;
                    case "CacheConnection":
                        properties.cacheConnection = stringToBoolean(value);
                        break;
                }
            }
        }

        if (hasColumnId && hasColumnLabel) {
            properties.columns = columns;
        }

        return hasTable && hasConnectionString ? properties : undefined;
    }

    private loadVariable(document: t.BaseMDMDocument, parent: t.IMDMObject, element: Element, specifiedUsageType?: t.VariableUsageFlags): t.Variable {
        // name
        let name = getElementAttribute(element, "name") ?? "";
        if (name === "") {
            this.error(errorMessages.missingAtrribute,  element.tagName, "name");
        }
        // id
        let id = getElementAttribute(element, "id");
        // ref
        let ref = getElementAttribute(element, "ref");
        if (ref) {
            let reference = document.getDefinition(ref, isMDMVariable);
            let varObj = this.factory.createVariable(document, parent, name, t.DataTypes.none, id) as Mutable<t.Variable>;
            varObj.reference = reference;
            if (reference) {
                varObj.dataType = reference.dataType;
                varObj.usageType = specifiedUsageType !== undefined ? specifiedUsageType : reference.usageType;
            }
            else {
                this.delay(() => {
                    let reference = document.getDefinition(ref, isMDMVariable);
                    if (reference) {
                        varObj.reference = reference;
                        varObj.usageType = specifiedUsageType !== undefined ? specifiedUsageType : reference.usageType;
                        varObj.dataType = reference.dataType;
                    }
                    else {
                        this.error(errorMessages.missingReferenceVariable, ref!);
                    }
                });
            }
            return varObj;
        }
        // data type
        let dataType = getElementAttribute<number>(element, "type", stringToInteger) ?? 0;
        const variable = this.factory.createVariable(document, parent, name, dataType, id) as Mutable<t.Variable>;
        // min
        variable.minValue = xmlValueToMdmValueNoBoolean(getElementAttribute(element, "min"), getElementAttribute(element, "mintype"));
        // max
        variable.maxValue = xmlValueToMdmValueNoBoolean(getElementAttribute(element, "max"), getElementAttribute(element, "maxtype"));
        // usagetype
        variable.usageType = getElementAttribute<number>(element, "usagetype", stringToInteger) ?? (specifiedUsageType ?? t.VariableUsageFlags.variable);
        // precisions
        variable.precisions = getElementAttribute<number>(element, "precisions", stringToInteger);
        // scale
        variable.scale = getElementAttribute<number>(element, "scale", stringToInteger);
        // db
        variable.dbDataType = getElementAttribute<number>(element, "dbdatatype", stringToInteger);
        // expression
        variable.expression = getElementAttribute(element, "expression");
        // validation
        variable.validation = getElementAttribute(element, "validation");
        // rangeexp
        variable.rangeExpression = getElementAttribute(element, "rangeexp");
        // autoelements
        variable.autoElements = stringToBoolean(getElementAttribute(element, "autoelements"));
        // defaultanswer
        variable.defaultAnswer = xmlValueToMdmValue(getElementAttribute(element, "defaultanswer"), getElementAttribute(element, "defaultanswer_type"));
        // initialAnswer
        variable.initialAnswer = xmlValueToMdmValue(getElementAttribute(element, "initialanswer"), getElementAttribute(element, "initialanswer_type"));
        // sourceType
        variable.sourceType = stringToInteger(getElementAttribute(element, "sourcetype")) ?? t.SourceTypes.none;
        // no-casedata
        variable.hasCaseData = !stringToBoolean(getElementAttribute(element, "no-casedata"));
        // unversioned
        variable.versioned = !stringToBoolean(getElementAttribute(element, "unversioned"));
        // infotype
        variable.infoType = stringToInfoType(getElementAttribute(element, "infotype"));
        // usagetype
        variable.usageType = stringToInteger(getElementAttribute(element, "usagetype")) ?? t.VariableUsageFlags.variable;

        this.loadLabeledObject(variable, element);

        forEachChildElement(element, e => {
            switch (e.tagName) {
                case "categories":
                    variable.elements = this.loadElements(document, variable, variable, e);
                    break;
                case "db":
                    variable.dbQuestionProperties = this.loadDbProperties(e);
                    break;
                case "ranges":
                    variable.ranges = this.loadRanges(e, dataType);
                    break;
                case "axis":
                    variable.axisExpression = getElementAttribute(e, "expression");
                    break;
                case "helperfields":
                    variable.helperFields = this.loadTypes(e, variable, "variable", ele => {
                        const item = this.loadVariable(document, variable, ele);
                        if (item.name === "Codes") {
                            if (!item.reference) {
                                this.delay(() => {
                                    if (item.reference && isMDMVariable(item.reference) && bitAnd(item.reference.usageType, t.VariableUsageFlags.coding)) {
                                        variable.codes = item;
                                    }
                                    else {
                                        variable.helperFields.update(item);
                                    }
                                });
                            }
                            else {
                                variable.codes = item;
                            }
                            return undefined;
                        }
                        return item;
                    });
                    break;
            }
        }, this);

        return variable;
    }

    private loadMDMElementInstance(document: t.BaseMDMDocument, parent: t.IMDMObject, element: Element): t.MDMElementInstance {
        let instance = this.factory.createMDMElementInstace(document, parent, getElementAttribute(element, "fullname")) as Mutable<t.MDMElementInstance>;
        let properties = getFirstChildByTagName(element, "properties");
        if (properties) {
            instance.properties = this.loadProperties(document, properties);
        }
        return instance;
    }

    private loadMDMElementInstances(document: t.BaseMDMDocument, parent: t.IMDMObject, element: Element): t.MDMElementInstances {
        let list = this.loadMDMList(element, "element", e => this.loadMDMElementInstance(document, parent, e)) as Mutable<t.MDMElementInstances>;
        list.objectFlag = t.ObjectFlags.elementInstances;
        return list;
    }

    private loadVariableInstance(document: t.BaseMDMDocument, element: Element): void {
        let variableId = getElementAttribute(element, "variable");
        let variable = document.getDefinition(variableId, isMDMVariable);
        if (!variableId) {
            this.error(errorMessages.missingAtrribute, "variable");
        }
        else if (!variable) {
            this.error(errorMessages.missingReferenceVariable, variableId);
        }
        else {
            let instance = this.factory.createVariableInstance(document, variable, getElementAttribute(element, "fullname"), getElementAttribute(element, "id")) as Mutable<t.VariableInstance>;
            let properties = getFirstChildByTagName(element, "properties");
            if (properties) {
                instance.properties = this.loadProperties(document, properties);
            }
            let categories = getFirstChildByTagName(element, "categories");
            if (categories) {
                instance.elements = this.loadMDMElementInstances(document, instance, categories);
            }
            (document.mapping as t.VariableInstance[]).push(instance);
        }
    }

    private loadPage(document: t.BaseMDMDocument, parent: t.IMDMObject, element: Element): t.Page {
        const items: t.Field[] = [];
        const name = getElementAttribute(element, "name") ?? "";
        const uuid = getElementAttribute(element, "id") ?? getUuid();
        const page = this.factory.createPage(document, parent, name, uuid, items) as Mutable<t.Page>;

        // ref
        let ref = getElementAttribute(element, "ref");
        if (ref) {
            const reference = document.getDefinition(ref, isMDMPage);
            if (reference) {
                page.reference = reference;
            }
            else {
                this.delay(() => {
                    let refObj = document.getDefinition(ref, isMDMPage);
                    if (refObj) {
                        page.reference = refObj;
                    }
                    else {
                        this.error(errorMessages.missingReferenceVariable, ref!);
                    }
                });
            }
            return page;
        }

        this.loadLabeledObject(page, element);

        let itemNodes = element.getElementsByTagName("item");
        for (let i = 0; i < itemNodes.length; i++) {
            let name = getElementAttribute(itemNodes[i], "name") ?? "";

            if (name === "") {
                this.error(errorMessages.missingAtrribute, itemNodes[i].tagName, "name");
            }

            let id = getElementAttribute(itemNodes[i], "id") ?? getUuid();
            // 创建引用变量对象
            // 由于可能出现定义在引用后的情况，此处向 storedAction 添加回调函数，
            // 在 loadVariableDefinitions 函数完成定义读取后再进行引用查找。
            this.delay(() => {
                let ref = getElementAttribute(itemNodes[i], "ref");
                if (ref) {
                    const referenceObj = document.getDefinition(ref, isMDMVariable);
                    if (referenceObj) {
                        const variable = this.factory.createVariable(document, page, name, referenceObj.dataType, id) as Mutable<t.Variable>;
                        variable.reference = referenceObj;
                        page.update(variable);
                    }
                    else {
                        this.error(errorMessages.missingReferenceVariable, ref);
                    }
                }
                else {
                    this.error(errorMessages.missingAtrribute, itemNodes[i].tagName, "ref");
                }
            });
        }

        if (name === "") {
            this.error(errorMessages.missingAtrribute, element.tagName, "name");
        }

        return page;
    }

    private loadPages(document: t.BaseMDMDocument, parent: t.IMDMObject, element: Element): t.Pages {
        let name = getElementAttribute(element, "name") ?? "@pages";

        let items: t.Page[] = [];
        let deletedItems: t.Page[] = [];

        let deletedNode: Element | undefined;
        forEachChildElement(element, e => {
            if (e.tagName === "page") {
                items.push(this.loadPage(document, parent, e));
            }
            else if (e.tagName === "deleted") {
                deletedNode = e;
            }
            else {
                this.error(errorMessages.unknownChildElementTag, element.tagName, e.tagName);
            }
        }, this);

        // deleted
        if (deletedNode) {
            forEachChildElement(deletedNode, e => {
                if (e.tagName === "page") {
                    deletedItems.push(this.loadPage(document, parent, e));
                }
                else {
                    this.error(errorMessages.unknownChildElementTag, element.tagName, e.tagName);
                }
            }, this);
        }

        return this.factory.createPages(parent, name, getElementAttribute(element, "id"), items, deletedItems);
    }

    private loadDefinitions(document: t.BaseMDMDocument, element: Element): void {
        this.clearDelayedAction();
        // variable
        forEachChildElement(element, e => {
            switch (e.tagName) {
                case "variable":
                case "othervariable":
                case "multiplier-variable":
                    document.updateDefinition(this.loadVariable(document, document, e));
                    break;
                case "page":
                    document.updateDefinition(this.loadPage(document, document, e));
                    break;
                case "categories":
                    document.updateDefinition(this.loadElements(document, document, undefined, e));
                    break;
                default:
                    this.error(errorMessages.unknownElementTag);
                    break;
            }
        }, this);
        this.executeDelayedAction();
        this.clearDelayedAction();
    }

    private loadFieldLike(document: t.BaseMDMDocument, parent: t.IMDMObject, element: Element): t.Field {
        if (element.tagName === "compound") {
            return this.loadCompound(document, parent, element);
        }
        return this.loadMDMField(document, parent, element);
    }

    private loadClass(document: t.BaseMDMDocument, parent: t.IMDMObject, element: Element, ownerField?: t.Field, fieldCallback?: (e: t.IMDMLabeledObject) => boolean): t.Class {
        const obj = this.factory.createClass(document, parent, getElementAttribute(element, "name"), getElementAttribute(element, "id")) as Mutable<t.Class>;
        this.loadLabeledObject(obj, element);
        // namespace
        obj.namespace = getElementAttribute(element, "global-name-space") === "0";

        forEachChildElement(element, e => {
            switch (e.tagName) {
                case "types":
                    obj.types = this.loadTypes(e, obj, "categories", () => this.loadElements(document, obj, ownerField, e));
                    break;
                case "fields":
                    obj.fields = this.loadTypes(e, obj, undefined, e => {
                        const f = this.loadMDMField(document, obj, e) as Mutable<t.MDMField>;
                        if (ownerField) {
                            f.fullName = `${isMDMField(ownerField) ? ownerField.fullName : ownerField.name}[..].${f.name}`;
                        }
                        if (fieldCallback && !fieldCallback(f)) {
                            return undefined;
                        }
                        return f;
                    });
                    break;
                case "routings":
                    obj.routings = this.loadRoutings(document, e);
                    break;
                case "pages":
                    obj.pages = this.loadPages(document, obj, e);
                    break;
            }
        }, this);

        return obj;
    }

    private loadMDMField(document: t.BaseMDMDocument, parent: t.IMDMObject, element: Element): t.MDMField {
        let name = getElementAttribute(element, "name");
        if (!name) {
            this.error(errorMessages.missingAtrribute, element.tagName, "name");
            name = "";
        }
        const obj = this.factory.createMDMField(document, parent, name, t.DataTypes.none, getElementAttribute(element, "id")) as Mutable<t.MDMField>;
        // variable
        if (element.tagName === "variable") {
            let ref = getElementAttribute(element, "ref");
            if (ref) {
                let reference = document.getDefinition(ref, isMDMVariable);
                if (reference) {
                    obj.reference = reference;
                    obj.dataType = reference.dataType;
                    obj.elements = reference.elements;
                    obj.dbQuestionProperties = reference.dbQuestionProperties;
                }
                else {
                    this.error(errorMessages.missingReferenceVariable, ref);
                }
            }
            else {
                this.error(errorMessages.missingAtrribute, element.tagName, "ref");
            }
        }
        else if (element.tagName === "loop" || element.tagName === "grid") {
            this.loadLabeledObject(obj, element);
            obj.dataType = getElementAttribute(element, "type", stringToInteger) ?? t.DataTypes.none;
            obj.iteratorType = getElementAttribute(element, "iteratortype", stringToInteger);
            obj.isGrid = getElementAttribute(element, "isgrid") === "1";
            obj.rangeExpression = getElementAttribute(element, "rangeexp");
            obj.autoElements = stringToBoolean(getElementAttribute(element, "autoelements"));
            obj.dbQuestionType = getElementAttribute(element, "dbtype", stringToInteger);
            obj.dbDataType = getElementAttribute(element, "dbdatatype", stringToInteger);
            obj.upperBound = getElementAttribute(element, "upperbound");
            obj.lowerBound = getElementAttribute(element, "lowerbound");
            // class
            forEachChildElement(element, e => {
                if (e.tagName === "categories") {
                    obj.elements = this.loadElements(document, obj, obj, e);
                }
                else if (e.tagName === "class") {
                    obj.class = this.loadClass(document, obj, e, obj, field => {
                        if (field.name === "DBID") {
                            if (isMDMVariable(field) || isMDMField(field)) {
                                obj.dbQuestionProperties = field.dbQuestionProperties;
                                return false;
                            }
                        }
                        if (isMDMField(field) || isMDMCompound(field)) {
                            (field as Mutable<t.MDMField>).parent = obj;
                        }
                        obj.fields.update(field);
                        return true;
                    });
                }
                else if (e.tagName === "ranges") {
                    obj.ranges = this.loadRanges(e, t.DataTypes.long);
                }
            }, this);
        }
        return obj;
    }

    private loadCompound(document: t.BaseMDMDocument, parent: t.IMDMObject, element: Element): t.Compound {
        let name = getElementAttribute(element, "name") ?? "";
        let id = getElementAttribute(element, "id");
        let compound = this.factory.createCompound(document, parent, name, id) as Mutable<t.Compound>;
        compound.namespace = getElementAttribute(element, "global-name-space") === "0";
        this.loadLabeledObject(compound, element, prop => {
            if (prop.name === "DisplayOrientation") {
                compound.orientation = stringToInteger(prop.value?.toString());
            }
            return true;
        });

        forEachChildElement(element, e => {
            switch (e.tagName) {
                case "categories":
                    compound.elements = this.loadElements(document, compound, compound, e);
                    break;
                case "t.Types":
                    compound.types = this.loadTypes(e, compound, "categories", ele => this.loadElements(document, compound, compound, ele));
                    break;
                case "fields":
                    compound.fields = this.loadTypes(e, compound, undefined, ele => this.loadMDMField(document, compound, ele));
                    break;
                case "pages":
                    compound.pages = this.loadPages(document, compound, e);
                    break;
            }
        }, this);

        return compound;
    }

    private loadDesigns(document: Mutable<t.BaseMDMDocument>, element: Element): void {
        this.clearDelayedAction();
        forEachChildElement(element, e => {
            switch (e.tagName) {
                case "fields":
                    document.fields = this.loadTypes(e, document, undefined, ele => this.loadFieldLike(document, document, ele));
                    break;
                case "types":
                    document.types = this.loadTypes(e, document, "categories", ele => this.loadElements(document, document, undefined, ele));
                    break;
                case "pages":
                    document.pages = this.loadPages(document, document, e);
                    break;
                case "routings":
                    document.routings = this.loadRoutings(document, e);
                    break;
                case "properties":
                    document.properties = this.loadProperties(document, e);
                    break;
            }
        }, this);
        this.executeDelayedAction();
        this.clearDelayedAction();
    }

    private loadRouting(element: Element): t.Routing {
        let context = getElementAttribute(element, "context") ?? "";
        let mode = getElementAttribute(element, "interviewmodes", stringToInteger);
        let useKeyCode = getElementAttribute(element, "usekeycodes", stringToBoolean);

        let items: t.RoutingItem[] = [];
        let itemNodes = element.getElementsByTagName("ritem");
        for (let i = 0; i < itemNodes.length; i++) {
            const item = itemNodes[i];
            let itemName = getElementAttribute(item, "name");
            let itemItem = getElementAttribute(item, "item");
            if (!itemName || !itemItem) {
                this.error(errorMessages.missingAtrribute, itemNodes[i].tagName, joinString(!itemName ? "name" : undefined, !itemItem ? "item" : undefined));
            }
            else {
                items.push({ name: itemName, item: itemItem });
            }
        }

        if (context === "") {
            this.error(errorMessages.missingAtrribute, element.tagName, "context");
        }

        return this.factory.createRouting(context, mode, useKeyCode, items);
    }

    private loadScript(element: Element): t.Script {
        let name = getElementAttribute(element, "name") ?? "";
        let isDefault = getElementAttribute(element, "default", stringToBoolean) ?? false;
        let inner = getInnerText(element);

        if (name === "") {
            this.error(errorMessages.missingAtrribute, element.tagName, "name");
        }

        return this.factory.createScript(name, inner.text, isDefault);
    }

    private loadScriptType(element: Element): t.ScriptType {
        let type = getElementAttribute(element, "type") ?? "";
        let context = getElementAttribute(element, "context") ?? "";
        let mode = getElementAttribute(element, "interviewmodes", stringToInteger) ?? 0;
        let useKeyCode = getElementAttribute(element, "usekeycodes", stringToBoolean) ?? false;

        if (type === "" || context === "") {
            this.error(errorMessages.missingAtrribute, element.tagName, joinString(type === "" ? "type" : undefined, context === "" ? "context" : undefined));
        }

        let items: t.Script[] = [];
        let itemNodes = element.getElementsByTagName("script");
        for (let i = 0; i < itemNodes.length; i++) {
            items.push(this.loadScript(itemNodes[i]));
        }

        return this.factory.createScriptType(type, context, mode, useKeyCode, items);
    }

    private loadScripts(element: Element): t.Scripts {
        return this.loadMDMList(element, "scripttype", this.loadScriptType);
    }

    private loadRoutings(document: t.BaseMDMDocument, element: Element): t.Routings {
        let name = getElementAttribute(element, "name");
        let scripts: t.Scripts | undefined;
        let items: t.Routing[] = [];

        let scriptsNode = getFirstChildByTagName(element, "scripts");
        if (scriptsNode) {
            scripts = this.loadScripts(scriptsNode);
        }

        let routingItems = element.getElementsByTagName("routing");
        for (let i = 0; i < routingItems.length; i++) {
            items.push(this.loadRouting(routingItems[i]));
        }

        let properties: t.Properties | undefined;
        let propertiesNode = getFirstChildByTagName(element, "properties");
        if (propertiesNode) {
            properties = this.loadProperties(document, propertiesNode);
        }

        return this.factory.createRoutings(document, name, scripts, properties, items);
    }

    private loadAlias(document: t.BaseMDMDocument, element: Element): t.Alias {
        let items: t.SubAlias[] = [];

        // subalias
        let subAliasNodes = element.getElementsByTagName("subalias");
        for (let i = 0; i < subAliasNodes.length; i++) {
            const node = subAliasNodes[i];
            let index = getElementAttribute(node, "index", stringToInteger);
            let name = getElementAttribute(node, "name");
            if (!index || !name) {
                this.error(errorMessages.missingAtrribute, node.tagName, joinString(index === undefined ? "index" : undefined, !name ? "name" : undefined));
            }
            else {
                items.push({ index, name });
            }
        }

        const alias = this.factory.createAlias(document, items) as Mutable<t.Alias>;

        // properties
        let properties = getFirstChildByTagName(element, "properties");
        if (properties) {
            alias.properties = this.loadProperties(document, properties);
        }

        // nativevalues
        let nativeValues = getFirstChildByTagName(element, "nativevalues");
        if (nativeValues) {
            let values: t.NativeValue[] = [];
            let nativeNodes = nativeValues.getElementsByTagName("nativevalue");
            for (let i = 0; i < nativeNodes.length; i++) {
                let fullName = getElementAttribute(nativeNodes[i], "fullname");
                let value = getElementAttribute(nativeNodes[i], "value");
                if (!fullName || !value) {
                    this.error(errorMessages.missingAtrribute, nativeNodes[i].tagName, joinString(!fullName ? "fullname" : undefined, !value ? "value" : undefined));
                }
                else {
                    values.push({ fullName, value });
                }
            }
            alias.nativeValues = values;
        }

        return alias;
    }

    private loadDataSource(document: t.BaseMDMDocument, element: Element): t.DataSource {
        let source = this.factory.createDataSource(
            getElementAttribute(element, "name") ?? "",
            getElementAttribute(element, "id") ?? getUuid(),
            getElementAttribute(element, "dblocation") ?? "",
            getElementAttribute(element, "cdscname") ?? "",
            getElementAttribute(element, "project") ?? ""
        ) as Mutable<t.DataSource>;

        if (element.hasChildNodes()) {
            let aliasMap = this.loadMDMList(element, "var", e => this.loadAlias(document, e)) as Mutable<t.AliasMap>;
            aliasMap.objectFlag = t.ObjectFlags.aliasMap;
            source.aliasMap = aliasMap;
        }

        return source;
    }

    private loadDataSources(document: Mutable<t.BaseMDMDocument>, element: Element): void {
        let sourceNodes = element.getElementsByTagName("connection");

        for (let i = 0; i < sourceNodes.length; i++) {
            (document.dataSources as t.DataSource[]).push(this.loadDataSource(document, sourceNodes[i]));
        }

        let defaultName = getElementAttribute(element, "default");
        let defaultSource: t.DataSource | undefined;
        if (defaultName) {
            defaultSource = (document.dataSources as t.DataSource[]).find(e => e.name === defaultName);
            if (!defaultSource) {
                this.error(errorMessages.missingDatasource, defaultName);
            }
            else {
                (document.dataSources as Mutable<t.DataSources>).default = defaultSource;
                if (defaultSource.aliasMap) {
                    document.aliasMap = defaultSource.aliasMap;
                }
            }
        }

    }

    private loadLanguage(document: t.BaseMDMDocument, element: Element): t.Language {
        let name = getElementAttribute(element, "name") ?? DefaultOptions.mdmLoaderOptions.defaultLanguage.longCode;
        let id = getElementAttribute(element, "id") ?? getUuid();
        let localLanguage = (longCodeTextToMetadataLanguage(name) ?? shortCodeTextToMetadataLanguage(name)) ?? this._options.defaultLanguage;
        const language = this.factory.createLanguage(document, localLanguage, id) as Mutable<t.Language>;
        let properties = getFirstChildByTagName(element, "properties");
        if (properties) {
            language.properties = this.loadProperties(document, properties);
        }
        let alternatives = getFirstChildByTagName(element, "alternatives");
        if (alternatives) {
            language.alternatives = this.loadMDMList(alternatives, "alternative", e => getElementAttribute(e, "name"));
        }
        return language;
    }

    private loadLanguages(document: t.BaseMDMDocument, element: Element): t.Languages {
        const languages = this.loadMDMList(element, "language", e => this.loadLanguage(document, e)) as Mutable<t.Languages>;
        languages.objectFlag = t.ObjectFlags.languages;
        languages.document = document;
        languages.base = getElementAttribute(element, "base") ?? DefaultOptions.mdmLoaderOptions.defaultLanguage.longCode;
        languages.current = languages.base;
        let properties = getFirstChildByTagName(element, "properties");
        if (properties) {
            languages.properties = this.loadProperties(document, properties);
        }
        return languages;
    }

    private loadContext(document: t.BaseMDMDocument, element: Element): t.Context {
        let name = getElementAttribute(element, "name") ?? DefaultOptions.mdmLoaderOptions.defaultContext;
        let alternative = getFirstChildByTagName(element, "alternatives");
        let context = this.factory.createContext(document, name, t.ContextUsageFlags.labels) as Mutable<t.Context>;
        if (alternative) {
            const alt = this.loadMDMList(alternative, "alternative", e => getElementAttribute(e, "name")) as Mutable<t.ContextAlternatives>;
            alt.objectFlag = t.ObjectFlags.contextAlternatives;
            context.alternatives = alt;
        }
        return context;
    }

    private loadContexts(document: t.BaseMDMDocument, element: Element): t.Contexts {
        const contexts = this.loadMDMList(element, "context", e => this.loadContext(document, e)) as Mutable<t.Contexts>;
        contexts.objectFlag = t.ObjectFlags.contexts;
        contexts.base = getElementAttribute(element, "base") ?? DefaultOptions.mdmLoaderOptions.defaultContext;
        contexts.current = contexts.base;
        contexts.document = document;
        let properties = getFirstChildByTagName(element, "properties");
        if (properties) {
            contexts.properties = this.loadProperties(document, properties);
        }
        return contexts;
    }

    private loadUser(element: Element): t.User {
        let name = getElementAttribute(element, "name") ?? "mrStudio.exe";
        let version = getElementAttribute(element, "fileversion") ?? this._options.mdmVersion;
        let comment = getElementAttribute(element, "comment") ?? "";
        return this.factory.createUser(name, version, comment);
    }

    private loadSaveLog(element: Element): t.SaveLog {
        let version = getElementAttribute(element, "fileversion") ?? this._options.mdmVersion;
        let versionset = getElementAttribute(element, "versionset") ?? "";
        let userName = getElementAttribute(element, "username") ?? this._options.userName;
        let date = getElementAttribute(element, "date") ?? "";
        let count = getElementAttribute(element, "count", stringToInteger) ?? 1;
        const saveLog = this.factory.createSaveLog(date, versionset, version, userName, count) as Mutable<t.SaveLog>;
        let user = getFirstChildByTagName(element, "user");
        if (user) {
            saveLog.user = this.loadUser(user);
        }
        return saveLog;
    }

    private loadSaveLogs(element: Element): t.SaveLogs {
        const logs: t.SaveLog[] = [];
        let logNodes = element.getElementsByTagName("savelog");
        for (let i = 0; i < logNodes.length; i++) {
            logs.push(this.loadSaveLog(logNodes[i]));
        }
        return logs;
    }

    private loadDocument(document: Mutable<t.BaseMDMDocument> | undefined, url: string, content?: string): t.BaseMDMDocument {
        if (!document) {
            document = this.factory.createBaseDocument() as Mutable<t.BaseMDMDocument>;
        }
        document.url = url;
        document.projectDirectory = dirname(url);
        // Document
        let roots = (content ? readXmlContent(content) : readXml(url))?.documentElement?.getElementsByTagName("mdm:metadata");
        let root: Element | undefined = roots && roots.length > 0 ? roots[0] : undefined;
        if (!root) {
            this.error(errorMessages.missingRootNode);
        }
        else {
            // mdm_createversion
            document.currentVersion = getElementAttribute(root, "mdm_createversion") ?? DefaultOptions.mdmLoaderOptions.mdmVersion;
            // mdm_lastversion
            document.lastUpdatedByVersion = getElementAttribute(root, "mdm_lastversion") ?? DefaultOptions.mdmLoaderOptions.mdmVersion;
            // id
            document.uuid = getElementAttribute(root, "id") ?? getUuid();
            // data_version
            document.mdmVersion = getElementAttribute(root, "data_version") ?? "9";

            const thisArg = this;
            forEachChildElement(root, e => {
                switch (e.tagName) {
                    case "datasources":
                        this.loadDataSources(document!, e);
                        break;
                    case "properties":
                        forEachChildElement(e, prop => {
                            let property = this.loadProperty(document!, prop);
                            document!.properties.addNew(property.context, property);
                        }, thisArg);
                        break;
                    case "templates":
                        forEachChildElement(e, node => {
                            let name = getElementAttribute(node, "name");
                            let context = getElementAttribute(node, "context");
                            let value = getElementAttribute(node, "value");
                            if (!name || !context || !value) {
                                this.error(errorMessages.missingAtrribute,
                                    node.tagName,
                                    joinString(!name ? "name" : undefined, !context ? "context" : undefined, !value ? "value" : undefined));
                            }
                            else {
                                document!.templates.addNew(document!, context, name, context, value);
                            }
                        }, thisArg);
                        break;
                    case "styles":
                        document!.styles = this.loadStyles(e, "styles");
                        break;
                    case "labelstyles":
                        document!.labelStyles = this.loadStyles(e, "labelstyles");
                        break;
                    case "labels":
                        document!.labels = this.loadLabels(document!, e);
                        break;
                    case "definition":
                        this.loadDefinitions(document!, e);
                        break;
                    case "system":
                        document!.systemVariables = this.loadTypes(e, document, "class", () => this.loadClass(document!, document!, e));
                        break;
                    case "systemrouting":
                        document!.systemRouting = this.loadRoutings(document!, e);
                        break;
                    case "mappings":
                        let varInstanceNodes = e.getElementsByTagName("varinstance");
                        for (let i = 0; i < varInstanceNodes.length; i++) {
                            this.loadVariableInstance(document!, varInstanceNodes[i]);
                        }
                        break;
                    case "design":
                        this.loadDesigns(document!, e);
                        break;
                    case "languages":
                        document!.languages = this.loadLanguages(document!, e);
                        break;
                    case "contexts":
                        document!.contexts = this.loadContexts(document!, e);
                        break;
                    case "labeltypes":
                        document!.labelTypes = this.loadContexts(document!, e);
                        break;
                    case "routingcontexts":
                        document!.routingContexts = this.loadContexts(document!, e);
                        break;
                    case "scripttypes":
                        document!.scriptTypes = this.loadContexts(document!, e);
                        break;
                    case "savelogs":
                        document!.saveLogs = this.loadSaveLogs(e);
                        break;
                    case "atoms":
                        let atomNodes = e.getElementsByTagName("atom");
                        for (let i = 0; i < atomNodes.length; i++) {
                            let name = getElementAttribute(atomNodes[i], "name");
                            if (name) {
                                (document!.atoms as string[]).push(name);
                            }
                            else {
                                this.error(errorMessages.missingAtrribute, atomNodes[i].tagName, "name");
                            }
                        }
                        break;
                    case "categorymap":
                        let categoryNodes = e.getElementsByTagName("categoryid");
                        for (let i = 0; i < categoryNodes.length; i++) {
                            const categoryId = categoryNodes[i];
                            let name = getElementAttribute(categoryId, "name");
                            let value = getElementAttribute(categoryId, "value", stringToInteger);
                            if (name && value !== undefined) {
                                document!.updateCategoryMap(name);
                            }
                            else {
                                this.error(errorMessages.missingAtrribute, categoryId.tagName, joinString(!name ? "name" : undefined, value === undefined ? "value" : undefined));
                            }
                        }
                        break;
                }
            }, thisArg);

        }
        return document;
    }

    public loadFromFile(url: string): t.BaseMDMDocument {
        return this.loadDocument(undefined, url);
    }

    public loadFromString(url: string, content: string): t.BaseMDMDocument {
        return this.loadDocument(undefined, url, content);
    }

    public loadTo(document: t.BaseMDMDocument, url: string, content?: string | undefined): void {
        this.loadDocument(document, url, content);
    }

    private tryReadScriptValue(value: types.Literal | types.Identifier | undefined): string | number | boolean | undefined {
        if (value?.isNumberLiteral()) {
            return value.value;
        }
        if (value?.isTrueLiteral()) {
            return true;
        }
        if (value?.isFalseLiteral()) {
            return false;
        }
        if (value?.isStringLiteral()) {
            return value.text;
        }
    }

    private tryReadMetadataValue<T>(value: types.Literal | types.Identifier | undefined, converter?: (value: string) => T | undefined): T | undefined {
        if (!value) {
            return undefined;
        }
        if (converter) {
            return converter(value.text);
        }
    }

    private tryConvertMetadataValueToNumber(value?: types.Literal | types.Identifier): number | undefined {
        if (value?.isStringLiteral()) {
            return stringIsNumber(value.text) ? stringToNumber(value.text) : undefined;
        }
        if (value?.isNumberLiteral()) {
            return value.value;
        }
        if (value?.isTrueLiteral()) {
            return 1;
        }
        if (value?.isFalseLiteral()) {
            return 0;
        }
    }

    private getMetadataDocumentLclObject(areaName: string): types.MDMAreaName | undefined {
        return this._document?.lcl.find(e => e.areaName.text.toLowerCase() === areaName.toLowerCase());
    }

    // load from script
    // 此部分方法只完成从0开始新建的元数据文档，不适用更新已有的元数据文档。

    /**
     * 将脚本解析的结果节点读取到MDM对象中
     * @param node 脚本解析器的解析结果节点
     * @param loader 节点读取函数，如果产生错误，在函数体内进行处理，并返回`undefined`
     * @returns
     */
    private loadMDMListFromScript<T extends types.MDMArealBase, U>(node: types.MDMArealLike<T>, loader: (node: T, area: string) => U | undefined, ...preElements: U[]): t.MDMListOf<U> {
        let entries: U[] = [...preElements];
        node.forEachObject((obj, area) => {
            let areaObj = this.getMetadataDocumentLclObject(area);
            if (!areaObj) {
                this.error(errorMessages.unknownAreaName, {
                    start: (obj.areaName ? obj.areaName.start : obj.start),
                    length: (obj.areaName ? obj.areaName.getWidth() : obj.getWidth())
                }, area);
            }
            else {
                let result = loader(obj, area);
                if (result) {
                    entries.push(result);
                }
            }
        });
        return this.factory.createMDMList(entries);
    };

    private loadPropertyFromScript(node: types.MDMProperty, context: string, document: t.BaseMDMDocument): t.Property {
        let value: t.PropertyValue;
        if (node.value.isNullLiteral()) {
            value = "null";
        }
        else if (node.value.isNumberLiteral()) {
            value = node.value.value;
        }
        else if (node.value.isTrueLiteral()) {
            value = "true";
        }
        else if (node.value.isFalseLiteral()) {
            value = "false";
        }
        else if (node.value.isStringLiteral()) {
            value = `"${node.value.text}"`;
        }
        else {
            value = node.value.text;
        }
        document.updateAtom(node.name.text);
        return this.factory.createProperty(node.name.text, context, value);
    }

    private loadPropertiesFromScript(node: types.MDMProperties, document: t.BaseMDMDocument): t.Properties {
        let items: t.Property[] = [];
        node.forEachItem(p => items.push(this.loadPropertyFromScript(p, document.contexts.current, document)));
        let entries = this.loadMDMListFromScript<types.MDMProperties, t.ContextMapItem<t.Property[]>>(node, (n, c) => {
            let items: t.Property[] = [];
            n.forEachItem(p => items.push(this.loadPropertyFromScript(p, c, document)));
            return { context: c, value: items };
        }, { context: this._state.currentContext, value: items });
        return this.factory.createProperties(document, undefined, node.name.text, entries);
    }

    // Template的值只接受字符串
    private loadTemplateFromScript(node: types.MDMTemplate, document: t.BaseMDMDocument, context: string): t.Template {
        document.updateAtom(node.name.text);
        return this.factory.createTemplate(document, node.name.text, context, node.value.text);
    }

    private loadTemplatesFromScript(node: types.MDMTemplates, document: t.BaseMDMDocument): t.Templates {
        let items: t.Template[] = [];
        node.forEachItem(t => items.push(this.loadTemplateFromScript(t, document, document.contexts.current)));
        let entries = this.loadMDMListFromScript<types.MDMTemplates, t.ContextMapItem<t.Template[]>>(node, (n, c) => {
            let items: t.Template[] = [];
            n.forEachItem(t => items.push(this.loadTemplateFromScript(t, document, c)));
            return { context: c, value: items };
        }, { context: this._state.currentContext, value: items });
        return this.factory.createTemplates(document, entries);
    }

    private loadLabelObjectFromScript(node: types.MDMLabel, context: string): t.LabelObject {
        return this.factory.createLabelObject(context, this._state.currentLanguage.longCode, node.label.text);
    }

    private loadLabelFromScript(node: types.MDMLabel, document: t.BaseMDMDocument): t.Label {
        let entries = this.loadMDMListFromScript(node, (n, c) => this.loadLabelObjectFromScript(n, c), this.loadLabelObjectFromScript(node, this._state.currentContext));
        return this.factory.createLabel(document, this._state.currentContext, entries);
    }

    private loadLabelsFromScript(node: types.MDMLabel, document: t.BaseMDMDocument): t.Labels {
        return this.factory.createLabels(document, [ { context: this._state.currentLabelType, value: this.loadLabelFromScript(node, document) } ]);
    }

    private loadCellStyleFromScript(node: types.MDMCellStyle, context: string): t.CellStyle {
        let cellStyle = this.factory.createCellStyle(context);
        cellStyle.width = node.width?.value.text;
        cellStyle.height = node.height?.value.text;
        cellStyle.borderColor = node.borderColor?.value.text;
        cellStyle.borderTopColor = node.borderTopColor?.value.text;
        cellStyle.borderRightColor = node.borderRightColor?.value.text;
        cellStyle.borderBottomColor = node.borderBottomColor?.value.text;
        cellStyle.borderLeftColor = node.borderLeftColor?.value.text;
        cellStyle.bgColor = node.bgColor?.value.text;
        cellStyle.borderStyle = this.tryReadMetadataValue(node.borderStyle?.value, stringToBorderStyle);
        cellStyle.borderTopStyle = this.tryReadMetadataValue(node.borderTopStyle?.value, stringToBorderStyle);
        cellStyle.borderRightStyle = this.tryReadMetadataValue(node.borderRightStyle?.value, stringToBorderStyle);
        cellStyle.borderBottomStyle = this.tryReadMetadataValue(node.borderBottomStyle?.value, stringToBorderStyle);
        cellStyle.borderLeftStyle = this.tryReadMetadataValue(node.borderLeftColor?.value, stringToBorderStyle);
        cellStyle.borderWidth = this.tryConvertMetadataValueToNumber(node.borderWidth?.value);
        cellStyle.borderTopWidth = this.tryConvertMetadataValueToNumber(node.borderTopWidth?.value);
        cellStyle.borderRightWidth = this.tryConvertMetadataValueToNumber(node.borderRightWidth?.value);
        cellStyle.borderBottomWidth = this.tryConvertMetadataValueToNumber(node.borderBottomWidth?.value);
        cellStyle.borderLeftWidth = this.tryConvertMetadataValueToNumber(node.borderLeftWidth?.value);
        cellStyle.padding = this.tryConvertMetadataValueToNumber(node.padding?.value);
        cellStyle.paddingTop = this.tryConvertMetadataValueToNumber(node.paddingTop?.value);
        cellStyle.paddingRight = this.tryConvertMetadataValueToNumber(node.paddingRight?.value);
        cellStyle.paddingBottom = this.tryConvertMetadataValueToNumber(node.paddingBottom?.value);
        cellStyle.paddingLeft = this.tryConvertMetadataValueToNumber(node.paddingLeft?.value);
        cellStyle.wrap = this.tryReadMetadataValue(node.wrap?.value, stringToBoolean);
        cellStyle.colSpan = this.tryConvertMetadataValueToNumber(node.colSpan?.value);
        cellStyle.rowSpan = this.tryConvertMetadataValueToNumber(node.rowSpan?.value);
        cellStyle.repeatHeader = this.tryConvertMetadataValueToNumber(node.repeatHeader?.value);
        cellStyle.repeatSideHeader = this.tryConvertMetadataValueToNumber(node.repeatSideHeader?.value);
        return cellStyle;
    }

    private loadFontStyleFromScript(node: types.MDMFontStyle, context: string): t.FontStyle {
        let fontStyle = this.factory.createFontStyle(context);
        // effects
        let effects = 0;
        if (stringToBoolean(node.isUnderline?.text)) {
            effects |= t.FontStyleEffects.isUnderline;
        }
        if (stringToBoolean(node.isItalic?.value.text)) {
            effects |= t.FontStyleEffects.isItalic;
        }
        if (stringToBoolean(node.isBold?.value.text)) {
            effects |= t.FontStyleEffects.isBold;
        }
        if (stringToBoolean(node.isStrikethrough?.value.text)) {
            effects |= t.FontStyleEffects.isStrikethrough;
        }
        if (stringToBoolean(node.isOverline?.value.text)) {
            effects |= t.FontStyleEffects.isOverline;
        }
        if (stringToBoolean(node.isBlink?.value.text)) {
            effects |= t.FontStyleEffects.isBlink;
        }
        if (stringToBoolean(node.isSuperscript?.value.text)) {
            effects |= t.FontStyleEffects.isSuperscript;
        }
        if (stringToBoolean(node.isSubscript?.value.text)) {
            effects |= t.FontStyleEffects.isSubscript;
        }
        fontStyle.effects = effects;
        // size
        fontStyle.size = this.tryConvertMetadataValueToNumber(node.size?.value);
        // family
        fontStyle.family = node.family?.value.text;
        return fontStyle;
    }

    private loadControlStyleFromScript(node: types.MDMControlStyle, context: string): t.ControlStyle {
        let controlStyle = this.factory.createControlStyle(context);
        controlStyle.type = this.tryReadMetadataValue(node.type?.value, stringToControlType);
        controlStyle.readonly = this.tryReadMetadataValue(node.readOnly?.value, stringToBoolean);
        controlStyle.accelerator = node.accelerator?.value.text;
        return controlStyle;
    }

    private loadStyleFromScript(node: types.MDMStyle, context: string): t.Style {
        let style = this.factory.createStyle(context);
        // control style
        if (node.control) {
            style.control = this.loadControlStyleFromScript(node.control, context);
        }
        // font style
        if (node.font) {
            style.font = this.loadFontStyleFromScript(node.font, context);
        }
        // cell style
        if (node.cell) {
            style.cell = this.loadCellStyleFromScript(node.cell, context);
        }
        // properties
        style.color = node.color?.value.text;
        style.bgColor = node.bgColor?.value.text;
        style.hidden = this.tryReadMetadataValue(node.hidden?.value, stringToBoolean);
        style.align = this.tryReadMetadataValue(node.align?.value, stringToAlignment);
        style.verticalAlign = this.tryReadMetadataValue(node.verticalAlign?.value, stringToVerticalAlign);
        style.elementAlign = this.tryReadMetadataValue(node.elementAlign?.value, stringToElementAlign);
        style.indent = this.tryReadMetadataValue(node.indent?.value, stringToInteger);
        style.zIndex = this.tryReadMetadataValue(node.zIndex?.value, stringToInteger);
        style.cursor = this.tryReadMetadataValue(node.cursor?.value, stringToCursorType);
        style.image = node.image?.value.text;
        style.imagePosition = this.tryReadMetadataValue(node.imagePosition?.value, stringToImagePosition);
        style.orientation = this.tryReadMetadataValue(node.orientation?.value, stringToOrientation);
        style.columns = this.tryReadMetadataValue(node.columns?.value, stringToInteger);
        style.rows = this.tryReadMetadataValue(node.rows?.value, stringToInteger);
        style.width = node.width?.value.text;
        style.height = node.height?.value.text;
        return style;
    }

    private loadStylesFromScript(node: types.MDMStyle): t.Styles {
        let entries = this.loadMDMListFromScript<types.MDMStyle, t.ContextMapItem<t.Style>>(node, (n, c) => {
            return { context: c, value: this.loadStyleFromScript(n, c) };
        },
        { context: this._state.currentContext, value: this.loadStyleFromScript(node, this._state.currentContext) });
        return this.factory.createContextMap(entries);
    }

    private loadLabeledObjectFromScript<T extends t.IMDMLabeledObject>(node: types.MDMLabeledObject, flag: T["objectFlag"], document: t.BaseMDMDocument, parent: t.IMDMObject) {
        let obj = this.factory.createLabeledObject(document, parent, flag, node.name.text) as Mutable<T>;
        // properties
        if (node.properties) {
            obj.properties = this.loadPropertiesFromScript(node.properties, document);
        }
        // templates
        if (node.templates) {
            obj.templates = this.loadTemplatesFromScript(node.templates, document);
        }
        // style
        if (node.style) {
            obj.styles = this.loadStylesFromScript(node.style);
        }
        // labelstyles
        if (node.labelStyle) {
            obj.labelStyles = this.loadStylesFromScript(node.labelStyle);
        }
        // label
        if (node.label) {
            obj.labels = this.loadLabelsFromScript(node.label, document);
        }
        return obj;
    }

    private tryLookupNamedReferenceVariable(value: types.StringLiteral | types.MDMField | undefined, document: t.BaseMDMDocument, setter: (v: t.Variable) => void) {
        if (!value) {
            return;
        }
        if (value.isStringLiteral()) {
            let refName = value.text;
            let defs = document.getDefinitionByName(refName, isMDMVariable);
            if (defs.length === 0) {
                this.delay(() => {
                    let find = document.getDefinitionByName(refName, isMDMVariable);
                    if (find.length === 0) {
                        this.error(errorMessages.missingNamedReferenceVariable, {
                            start: value.start,
                            length: value.getWidth()
                        }, refName);
                    }
                    else if (find.length === 1) {
                        setter(find[0]);
                    }
                    else {
                        this.error(errorMessages.findMultipleNamedReferenceVariable, {
                            start: value.start,
                            length: value.getWidth()
                        }, refName);
                    }
                });
            }
            else if (defs.length === 1) {
                setter(defs[0]);
            }
            else {
                this.error(errorMessages.findMultipleNamedReferenceVariable, {
                    start: value.start,
                    length: value.getWidth()
                }, refName);
            }
        }
        else {
            setter(this.loadVariableFromScript(value, document));
        }
    }

    private loadMDMElementFromScript(node: types.MDMCategory, document: t.BaseMDMDocument, parent: t.IMDMObject, owner?: t.Field): t.MDMElement {
        let obj = this.loadLabeledObjectFromScript<t.MDMElement>(node, t.ObjectFlags.element, document, parent);
        obj.ownerField = owner;
        // category flag
        obj.flag = t.CategoryFlags.none;
        if (node.dk) {
            obj.flag |= t.CategoryFlags.dontknow;
        }
        if (node.ref) {
            obj.flag |= t.CategoryFlags.refuse;
        }
        if (node.na) {
            obj.flag |= t.CategoryFlags.noanswer;
        }
        if (node.other) {
            obj.flag |= t.CategoryFlags.other;
            // 允许没有 otherVariable
            this.tryLookupNamedReferenceVariable(node.other.field, document, v => obj.otherVariable = v);
        }
        if (node.multiplier) {
            obj.flag |= t.CategoryFlags.multiplier;
            // 允许没有 multiplierVariable
            this.tryLookupNamedReferenceVariable(node.multiplier.field, document, v => obj.multiplierVariable = v);
        }
        if (node.fix) {
            obj.flag |= t.CategoryFlags.fixedPosition;
        }
        if (node.nofilter) {
            obj.flag |= t.CategoryFlags.noFilter;
        }
        if (node.exclusive) {
            obj.flag |= t.CategoryFlags.exclusive;
        }
        // factor
        obj.factor = this.tryReadScriptValue(node.factor?.value);
        // expression
        obj.expression = node.expression?.value.text;
        obj.autoElements = !!node.expression?.deriveElements;
        // element type
        if (node.elementType) {
            obj.type = metadataCategoryElementTypeToElementType(node.elementType.elementTypeFlag);
        }
        // fix
        obj.fixed = !!node.fix;
        // keycode
        if (node.keycode) {
            obj.keyCode = node.keycode.value.text;
        }
        // nofilter
        if (node.nofilter) {
            obj.canFilter = false;
        }
        // 更新文档的categorymap
        document.updateCategoryMap(obj.fullName);
        return obj;
    }

    private getReadonlyMapItemByName<T extends t.IMDMLabeledObject>(map: ReadonlyMap<string, T>, name: string): T | undefined {
        for (const [, item] of map) {
            if (stringEqual(item.name, name)) {
                return item;
            }
        }
    }

    private loadMDMElementsFromScript(node: types.MDMField, document: t.BaseMDMDocument, parent: t.IMDMObject, ownerField?: t.Field | undefined): t.MDMElements;
    private loadMDMElementsFromScript(node: types.MDMCategory, document: t.BaseMDMDocument, parent: t.IMDMObject, ownerField?: t.Field | undefined): t.MDMElements;
    private loadMDMElementsFromScript(node: types.MDMCategories, document: t.BaseMDMDocument, parent: t.IMDMObject, ownerField?: t.Field | undefined): t.MDMElements;
    private loadMDMElementsFromScript(node: types.MDMField | types.MDMCategory | types.MDMCategories, document: t.BaseMDMDocument, parent: t.IMDMObject, ownerField?: t.Field | undefined): t.MDMElements {
        let nodeIsField = node.isMDMField();
        let elements: Mutable<t.MDMElements>;
        if (nodeIsField) {
            elements = this.loadLabeledObjectFromScript<t.MDMElements>(node as types.MDMField, t.ObjectFlags.elements, document, parent);
            elements.ownerField = ownerField;
            elements.displayOrder = metadataSortOrderToDisplayOrders((node as types.MDMField).categories);
        }
        else {
            elements = this.factory.createMDMElements(document, parent, ownerField);
            elements.displayOrder = metadataSortOrderToDisplayOrders(node as types.MDMSortOrder);
        }
        // [list_name] use list sublist ...
        if (node.is<types.MDMCategory>(types.SyntaxKind.mdmCategory)) {
            if (node.listName) {
                elements.name = node.listName.text;
            }
            if (node.list) {
                elements.referenceName = node.list.text;
                // 查询列表声明对象时，优先查找父级Field对象中的定义，再在文档中查找
                if (ownerField && isMDMField(ownerField) && ownerField.class) {
                    elements.reference = this.getReadonlyMapItemByName(ownerField.class.types, node.list.text);
                }
                if (!elements.reference) {
                    let refs = document.getDefinitionByName(node.list.text, isMDMElements);
                    if (refs.length > 0) {
                        this.error(errorMessages.unknownMetadataField, {
                            start: node.list.start,
                            length: node.getWidth()
                        }, node.list.text);
                    }
                    else if (refs.length === 1) {
                        elements.reference = refs[0];
                    }
                    else {
                        this.error(errorMessages.findMultipleNamedReferenceVariable, {
                            start: node.list.start,
                            length: node.list.getWidth(),
                        }, node.list.text);
                    }
                }
            }
            return elements;
        }
        let categories = nodeIsField ? (node as types.MDMField).categories : node as types.MDMCategories;
        // 添加 element
        categories.forEachElement(ele => {
            elements.update(this.loadMDMElementFromScript(ele, document, elements));
        });
        return elements;
    }

    private updateFieldTailProperties(node: types.MDMField, obj: Mutable<t.Field>): void {
        // precision
        if (node.precision) {
            obj.precisions = node.precision.value.value;
        }
        // codes
        if (node.codes) {
            // 创建"Codes"定义变量
            let codesVariable = this.factory.createVariable(obj.document, obj.document, "Codes", t.DataTypes.categorical);
            for (const ele of node.codes.value) {
                codesVariable.elements.update(this.loadMDMElementFromScript(ele, obj.document, codesVariable, codesVariable));
            }
            obj.document.updateDefinition(codesVariable);
            obj.codes = codesVariable;
        }
        // validation
        if (node.validation) {
            obj.validation = node.validation.text;
        }
        // expression
        if (node.expression) {
            obj.expression = node.expression.text;
            if (node.expression.deriveElements) {
                obj.autoElements = true;
            }
        }
        // initial answer
        if (node.initialAnswer) {
            obj.initialAnswer = this.tryReadScriptValue(node.initialAnswer.value);
        }
        // default answer
        if (node.defaultAnswer) {
            obj.defaultAnswer = this.tryReadScriptValue(node.defaultAnswer.value);
        }
        // axis
        if (node.axis) {
            obj.axisExpression = node.axis.text;
        }
        // noCaseData
        if (node.noCaseData) {
            obj.hasCaseData = false;
        }
        // helper fields
        if (node.helperFields) {
            let fields: t.Variable[] = [];
            for (const field of node.helperFields.fields) {
                fields.push(this.loadVariableFromScript(field, obj.document));
            }
        }
        // scale
        if (node.scale) {
            obj.scale = node.scale.value.value;
        }
        // unversioned
        if (node.unversioned) {
            obj.versioned = false;
        }
    }

    private loadFieldFromScript<T extends t.Field>(node: types.MDMField, flag: T["objectFlag"], document: t.BaseMDMDocument, parent: t.IMDMObject): Mutable<T> {
        let obj = this.loadLabeledObjectFromScript<T>(node, flag, document, parent) as Mutable<T>;
        obj.dataType = metadataFieldFlagToDataType(node.fieldFlag);
        // range
        // 如果上下限都是数字，并且有多个区间，则保存类似 <range/> 的XML对象。
        // 如果上下限包含字符串，则保存到rangeExpression属性。
        if (node.valueRange && node.valueRange.length > 0) {
            let nodeRanges = node.valueRange;
            let ranges: t.Range[] = [];
            for (const r of nodeRanges) {
                let lowerBound = r.lowerBoundary?.isNumberLiteral() ? r.lowerBoundary.value : undefined;
                let upperBound = r.upperBoundary?.isNumberLiteral() ? r.upperBoundary.value : undefined;
                if (lowerBound !== undefined || upperBound !== undefined) {
                    ranges.push({
                        lowerBound: lowerBound === undefined ? this._options.invalidValueLowerBoundary : lowerBound,
                        upperBound: upperBound === undefined ? this._options.invalidValueUpperBoundary : upperBound,
                    });
                }
            }
            if (ranges.length > 0) {
                obj.ranges = ranges;
            }
            else {
                obj.rangeExpression = `[${node.valueRange.map(e => e.text).join(", ")}}]`;
            }
        }
        this.updateFieldTailProperties(node, obj);
        return obj;
    }

    // 非 loop 变量读取为MDMVariable，Field的下级便变量引用MDMVariable
    private loadVariableFromScript(node: types.MDMField, document: t.BaseMDMDocument): t.Variable {
        let obj = this.loadFieldFromScript<t.Variable>(node, t.ObjectFlags.variable, document, document);
        // usage type
        if (node.usageType) {
            obj.usageType = metadataUsageTypeToVariableUsageType(node.usageType.typeFlag);
        }
        document.updateDefinition(obj);
        return obj;
    }

    private loadMDMFieldFromScript(node: types.MDMField, document: t.BaseMDMDocument, parent: t.IMDMObject) {
        // 本地 CategoryList 定义，更新到 document.types 中
        if (node.fieldFlag === types.MDMFieldFlags.define) {
        }
        // 本地非循环变量，需要先声明变量定义，再创建MDMField引用此变量
        if (node.fieldFlag !== types.MDMFieldFlags.categoricalLoop && node.fieldFlag !== types.MDMFieldFlags.numericLoop) {
            const variable = this.loadVariableFromScript(node, document);
            let obj = this.factory.createMDMField(document, document, node.name.text, variable.dataType) as Mutable<t.MDMField>;
            obj.reference = variable;
            return obj;
        }
        let obj = this.loadFieldFromScript(node, t.ObjectFlags.field, document, parent) as Mutable<t.MDMField>;
        obj.dataType = metadataFieldFlagToDataType(node.fieldFlag);
        // iterator type
        if (node.fieldFlag === types.MDMFieldFlags.categoricalLoop) {
            obj.iteratorType = t.IteratorTypes.categorical;
        }
        else if (node.fieldFlag === types.MDMFieldFlags.numericLoop) {
            obj.iteratorType = node.valueRange && node.valueRange.length > 0 ? t.IteratorTypes.numericRanges : t.IteratorTypes.number;
        }
        // usage type
        if (node.usageType) {
            obj.usageType = metadataUsageTypeToVariableUsageType(node.usageType.typeFlag);
        }
        // row / col
        if (node.row) {
            obj.orientation = t.DisplayOrientations.row;
        }
        if (node.column) {
            obj.orientation = t.DisplayOrientations.col;
        }
        return obj;
    }

    public loadFromScript(metadata: types.MDMDocument): t.BaseMDMDocument {
        const document = this.factory.createBaseDocument();
        // reset state variable
        this._state.currentContext = metadataUserContextFlagToString(metadata.context);
        this._state.currentLabelType = metadataLabelTypeFlagToString(metadata.labelType);
        // initialize
        initalizeMDMDocument(document, this.factory);
        return document;
    }

}

